package net.gdface.utils.encrypt;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import net.gdface.utils.Base64Utils;

import static net.gdface.utils.ConditionChecks.checkNotNull;
import static net.gdface.utils.ConditionChecks.checkArgument;

import java.nio.charset.Charset;
import java.util.Arrays;

public class AES128ECBNoPadding {
	private static final Charset UTF_8 = Charset.forName("UTF-8");
	/**
     * 加密
     * @param sSrc 输入数据,长度必须是16的倍数
     * @param sKey key,长度必须是16,24,32
     * @return 加密数据
     * @throws Exception
     */
    public static byte[] encrypt(byte[] sSrc, byte[] sKey) throws Exception {
    	checkArgument(null != sSrc,"sSrc is null");
        checkArgument(null != sKey,"sKey is null");
        // 判断Key是否为16位
        checkArgument(sKey.length == 16,"length of sKey must be 16 bytes");
        SecretKeySpec skeySpec = new SecretKeySpec(sKey, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");//"算法/模式/补码方式"
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        return cipher.doFinal(sSrc);
    }
    /**
     * 加密
     * @param sSrc 输入数据,长度必须是16的倍数
     * @param sKey key,长度必须是16,24,32
     * @return 加密数据(base64)
     * @throws Exception
     */
    public static String encrypt(String sSrc, String sKey) throws Exception {
        byte[] encrypted = encrypt(
        		checkNotNull(sSrc,"sSrc is null").getBytes(UTF_8),
    			checkNotNull(sKey,"sKey is null").getBytes(UTF_8));
        return Base64Utils.encode(encrypted);
    }
    /**
     * 解密
     * @param sSrc 输入加密数据
     * @param sKey key
     * @return 解密数据
     * @throws Exception
     */
    public  static byte[] decrypt(byte[] sSrc, byte[] sKey) throws Exception {
    	checkArgument(null != sSrc,"sSrc is null");
    	checkArgument(null != sKey,"sKey is null");
    	// 判断Key是否为16位
    	checkArgument(sKey.length == 16,"length of sKey must be 16 bytes");
    	SecretKeySpec skeySpec = new SecretKeySpec(sKey, "AES");
    	Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
    	cipher.init(Cipher.DECRYPT_MODE, skeySpec);
		return cipher.doFinal(sSrc);
    }
    /**
     * 解密
     * @param sSrc 输入加密数据(base64)
     * @param sKey key
     * @return 解密数据(base64)
     * @throws Exception
     */
    public static String decrypt(String sSrc, String sKey) throws Exception {
    	// sSrc为base64字符串,先用base64解密
    	byte[] decoded = decrypt(Base64Utils.decode(checkNotNull(sSrc,"sSrc is null")),
    			checkNotNull(sKey,"sKey is null").getBytes(UTF_8));
    	return new String(decoded,UTF_8);
    }
 
    /**
	 * 自动补全加密<br>
	 * 自动在加密数据前加4个字节保存数据长度,对数据长度自动补全到16倍数,
	 * 自动补全密钥到16,24,32字节,超过32字节的密钥抛出异常
	 * @param sSrc 输入数据,
	 * @param sKey key
	 * @return 加密数据
	 * @throws Exception
	 */
	public static byte[] wrapEncrypt(byte[] sSrc, byte[] sKey) throws Exception {
		checkArgument(null != sSrc,"sSrc is null");
		checkArgument(null != sKey,"sKey is null");
		checkArgument(sKey.length <= 32 ,"too long sKey");
		byte[] paddingKey = sKey;
		byte[] paddingSrc = new byte[sSrc.length + 4];
	    // 数据头部添加4字节保存sSrc.length(小端)
	    paddingSrc[0] = (byte) (sSrc.length);
	    paddingSrc[1] = (byte) (sSrc.length>>8);
	    paddingSrc[2] = (byte) (sSrc.length>>16);
	    paddingSrc[3] = (byte) (sSrc.length>>24);
	    System.arraycopy(sSrc, 0, paddingSrc, 4, sSrc.length);
		if(paddingSrc.length %16 != 0){
			// 填充到16的倍数
			paddingSrc = Arrays.copyOf(paddingSrc, (paddingSrc.length + 16 -1 )/16*16);
		}
		if(sKey.length%8 != 0){
			// 填充到16,24,32字节
			int padSize = Math.max(16, (sKey.length + 8 -1 )/8*8);
			paddingKey = Arrays.copyOf(sKey, padSize);
		}
		return encrypt(paddingSrc,paddingKey);
	}
    /**
	 * 自动补全加密<br>
	 * 自动在加密数据前加4个字节保存数据长度,对数据长度自动补全到16倍数,
	 * 自动补全密钥到16,24,32字节,超过32字节的密钥抛出异常
     * @param sSrc 输入数据
     * @param sKey key
     * @return 加密数据(base64)
     * @throws Exception
     */
    public static String wrapEncrypt(String sSrc, String sKey) throws Exception {
        byte[] encrypted = wrapEncrypt(
        		checkNotNull(sSrc,"sSrc is null").getBytes(UTF_8),
    			checkNotNull(sKey,"sKey is null").getBytes(UTF_8));
        return Base64Utils.encode(encrypted);
    }
    /**
     * 解密{@link #wrapEncrypt(byte[], byte[])}加密的数据
     * @param sSrc 输入加密数据
     * @param sKey key 自动补全密钥到16,24,32字节,超过32字节的密钥抛出异常
     * @return 解密数据
     * @throws Exception
     */
    public static byte[] wrapDecrypt(byte[] sSrc, byte[] sKey) throws Exception {
    	checkArgument(null != sSrc,"sSrc is null");
    	checkArgument(null != sKey,"sKey is null");
    	checkArgument(sKey.length <= 32 ,"too long sKey");
    	byte[] paddingKey = sKey;
    	if(sKey.length%8 != 0){
			// 填充到16,24,32字节
    		int padSize = Math.max(16, (sKey.length + 8 -1 )/8*8);
			paddingKey = Arrays.copyOf(sKey, padSize);
		}
		byte[] wrap	= decrypt(sSrc, paddingKey);
	    // 数据头部添加4字节保存sSrc.length(小端)
		int wrapLength= ((int)wrap[0] & 0X000000FF) | (((int) (wrap[1] << 8)) & 0X0000FF00) | (((int) (wrap[2] << 16)) & 0X00FF0000)   | (((int) (wrap[3] << 24)) & 0XFF000000);
		checkArgument(wrapLength >0 && wrapLength < wrap.length,"INVALID wrap length %s",wrapLength);
		byte[] data = new byte[wrapLength];
		System.arraycopy(wrap, 4, data, 0, wrapLength);
		return data;
    }
    
    /**
     * 解密{@link #wrapEncrypt(String, String)}加密的数据
     * @param sSrc 输入加密数据(base64)
     * @param sKey key
     * @return 解密数据(base64)
     * @throws Exception
     */
    public static String wrapDecrypt(String sSrc, String sKey) throws Exception {
    	// sSrc为base64字符串,先用base64解密
    	byte[] decoded = wrapDecrypt(Base64Utils.decode(checkNotNull(sSrc,"sSrc is null")),
    			checkNotNull(sKey,"sKey is null").getBytes(UTF_8));
    	return new String(decoded,UTF_8);
    }
	public static void main(String[] args) throws Exception {
    	
    	//此处使用AES-128-ECB加密模式，key需要为16位。
        String cKey = "WllNRVNTQUdFOTk@";
        // 需要加密的字串
        String cSrc = "@FACE@Android@2@20200218@V2.3.5.1@05352CF3086932E2@@            ";
        System.out.println("待加密的字串是：" + cSrc);
        // 加密
        String enString = AES128ECBNoPadding.encrypt(cSrc, cKey);
        System.out.println("加密后的字串是：" + enString);
 
        // 解密
        String DeString = AES128ECBNoPadding.decrypt(enString, cKey);
        System.out.println("解密后的字串是：" + DeString);
        
        System.out.println("/////////////////// WRAP TEST////////////////////////");
        
        String cKey2 = "WllNRVNTQUdFOTk";
        // 需要加密的字串
        String cSrc2 = "{\"channel\":\"1610176971640\",\"qrCodeSceneId\":\"16169887538401\",\"IDcardSceneId\":\"16169887538402\",\"publicKey\":\"04A866F61DA13F8FA893A5C44D131D051FECBAE2C2F045978A17C815E7B1DE108519F5D5A7DE7D17A42BD407BA6503CEFB4C18E1940FEF9153047F3798B8A8C87D\",\"privateKey\":\"00B69D059FDC1B65E41608A5CA72A527FBB2FBC67ECA87999B3F56E65BC46610EE\",\"position\":\"江苏省,南京市,鼓楼区\",\"verifierDept\":\"XXX医院\",\"verifierName\":\"XXX\",\"verifierPhone\":\"18888888888\",\"location\":\"32.0238、118.4643\"}";
        System.out.println("待加密的字串是：" + cSrc2 + " 长度:" + cSrc2.length());
        // 加密
        String enString2 = AES128ECBNoPadding.wrapEncrypt(cSrc2, cKey2);
        System.out.println("加密后的字串是：" + enString2);
 
        // 解密
        String deString2= AES128ECBNoPadding.wrapDecrypt(enString2, cKey2);
        System.out.println("解密后的字串是：" + deString2 + " 长度:" + deString2.length());      
	}
}
